;;; init-tools.el --- Tools settings -*- lexical-binding: t -*-
;;; Commentary:

;;; Code:

;; emacs 内置文件管理器
(use-package dired
  :ensure nil
  ;; :hook (dired-mode . dired-omit-mode)  ; 隐藏不必要的文件/目录
  :bind (:map dired-mode-map
              ("C-+" . #'dired-create-empty-file)
              ("@"   . #'dired-kill-subdir)
              ("M-p" . #'dired-preview-mode)
              ("H"   . #'dired-gitignore-mode))
  :custom
  (dired-kill-when-opening-new-dired-buffer t) ; 保持一个 dired 打开
  (dired-auto-revert-buffer t)          ; dired buffer 自动刷新
  (dired-movement-style 'cycle)         ; 循环选择
  (dired-dwim-target t)                 ; 猜测目标目录
  (dired-listing-switches
   "-l --almost-all --human-readable --group-directories-first --no-group --time-style=iso")
  (dired-recursive-copies 'always)      ; 递归复制，复制子目录
  (dired-recursive-delete 'top)         ; 递归删除，always 总是，top 询问，nil 不允许
  :config
  (when (eq system-type 'gnu/linux)
    (dired-async-mode 1))

  ;; 创建/删除文件后刷新 dired
  (defun +dired/dired-refresh-after-create (orig-fun &rest args)
    "Advice function to refresh dired buffer after creating a directory."
    (apply orig-fun args)
    (revert-buffer))
  (advice-add 'dired-create-directory :around #'+dired/dired-refresh-after-create)
  (advice-add 'dired-create-empty-file :around #'+dired/dired-refresh-after-create))

;; 在 dired 中隐藏添加在 .gitignore 的文件与路径
(use-package dired-gitignore
  :ensure t
  :commands (dired-gitignore-mode))

;; dired 预览文件
(use-package dired-preview
  :ensure t
  :commands (dired-preview-mode)
  :custom
  (dired-preview-delay 0.2))

;; dired 增强
(use-package dirvish
  :ensure (:host github :repo "alexluigit/dirvish")
  :hook (elpaca-after-init . dirvish-override-dired-mode)
  :bind (:map dirvish-mode-map
              ("TAB" . #'dirvish-subtree-toggle)
              ("s"   . #'dirvish-quicksort))
  :custom
  (dirvish-use-header-line nil)
  (dirvish-use-mode-line 'global)
  (dirvish-path-separators '("  ~" "  #" " > "))
  (dirvish-subtree-always-show-state t)
  (dirvish-subtree-state-style 'arrow)
  (dirvish-attributes                   ; 文件行可视元素
   '(nerd-icons file-time file-size subtree-state))
  (dirvish-side-attributes
   '(nerd-icons subtree-state))
  (dirvish-mode-line-format
   '(:left (path) :right (sort omit symlink index)))
  (dirvish-side-mode-line-format
   '(:left (sort) :right(omit)))
  :config
  (setq delete-by-moving-to-trash t
        dirvish-mode-line-height (+ (frame-char-height) 4))

  ;; 调整 bar 的高度
  (defun my-dirvish--mode-line-bar-img (fullscreenp header)
    "My custom implementation of `dirvish--mode-line-bar-img'.
    Create a bar image with height of `dirvish-mode-line-height'.
    If FULLFRAME-P, use the `cdr' of the value as height, otherwise
    use `car'.  If HEADER, use `dirvish-header-line-height' instead."
    (when (and (display-graphic-p) (image-type-available-p 'pbm))
      (let* ((hv (if header dirvish-header-line-height dirvish-mode-line-height))
             (ht (cond ((numberp hv) hv) (fullscreenp (cdr hv)) (t (car hv)))))
        (propertize
         " " 'display
         (ignore-errors
           (create-image
            (concat (format "P1\n%i %i\n" 4 ht) (make-string (* 4 ht) ?1) "\n")
            ;; 添加 :scale 1
            'pbm t :scale 1 :foreground "None" :ascent 'center))))))
  (advice-add 'dirvish--mode-line-bar-img :override #'my-dirvish--mode-line-bar-img))

(use-package isearch
  :ensure nil
  :bind (:map isearch-mode-map
              ([remap isearch-delete-char] . isearch-del-char)
              ("C-g" . isearch-cancel)
              ("C-c" . #'+isearch/peng-rerun-isearch)
              ("C-o" . #'+isearch/occur-from-isearch)
              ("C-p" . #'+isearch/project-search-from-isearch))
  :custom
  (isearch-lazy-count t)		; 显示如 10/100 这种状态
  (lazy-count-prefix-format "%s/%s ")
  :config
  (defun +isearch--default-string-advice (orig-fun &rest args)
    "Use selected text as the default search string in `isearch-mode'."
    (let ((forward (car args)))
      (if (and transient-mark-mode mark-active (not (eq (mark) (point))))
          (progn
            ;; 将选中文本加入搜索历史
            (isearch-update-ring (buffer-substring-no-properties (mark) (point)))
            ;; 取消选区
            (deactivate-mark)
            ;; 调用原始函数，保持参数数量一致
            (apply orig-fun args)
            ;; 根据 forward 决定方向
            (if (not forward)
                (isearch-repeat-backward)
              (goto-char (mark))
              (isearch-repeat-forward)))
        ;; 没有选区时，直接调用原函数
        (apply orig-fun args))))
  (advice-add 'isearch-mode :around #'+isearch--default-string-advice)

  (defun +isearch--repeat-warp (&rest _)
    "Wrap around when `isearch` fails, continuing search from the beginning or end."
    (unless isearch-success
      (isearch-repeat (if isearch-forward 'forward 'backward))))
  (advice-add 'isearch-repeat :after #'+isearch--repeat-warp))

;; 跨文件文本搜索与替换
(use-package color-rg
  :ensure (:host github :repo "manateelazycat/color-rg")
  :commands (color-rg-search-symbol-in-project)
  :config
  (when (eq system-type 'windows-nt)
    (setq color-rg-command-prefix "powershell")))

;; 阅读 epub 文件
(use-package nov
  :ensure t
  :mode ("\\.epub\\'" . nov-mode)
  :custom
  (nov-text-width 90)) ; 文本宽度

;; 小说模式
(use-package novel-mode
  :ensure (:host github :repo "TLINDEN/novel-mode")
  :bind((:map text-mode-map
              ("C-c C-z" . novel-mode))
        (:map novel-mode-map
              ("C-c C-z" . text-mode))))

;; 拼写检查
(use-package wucuo
  :ensure (:host github :repo "redguardtoo/wucuo")
  :hook (text-mode . #'wucuo-start)
  :bind (:map text-mode-map
              ("M-n" . #'flyspell-goto-next-error))
  :init
  (setq ispell-program-name "hunspell")
  ;; reset the hunspell so it STOPS querying locale!
  ;; "en_US" is the key to lookup in `ispell-local-dictionary-alist`
  (setq ispell-local-dictionary "en_US")
  ;; If `ispell-local-dictionary-alist' is nil, `ispell-local-dictionary' is passed
  ;; to hunpsell cli program as dictionary.
  ;; Get the dictionary path by running "hunspell -D" in shell.
  (setq ispell-local-dictionary-alist
        '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "en_US-large") nil utf-8)))
  ;; Set up the dictionaries.
  (setq ispell-hunspell-dictionary-alist ispell-local-dictionary-alist)
  ;; Only set if using Hunspell
  (when (string= ispell-program-name "hunspell")
    (setq ispell-extra-args
          (list "-p" (concat no-littering-etc-directory "excluded-words.dic"))))
  (setq wucuo-spell-check-buffer-predicate
        (lambda ()
          (not (memq major-mode
                     '(dired-mode
                       log-edit-mode
                       compilation-mode
                       help-mode
                       profiler-report-mode
                       prog-mode
                       speedbar-mode
                       gud-mode
                       calc-mode
                       Info-mode))))))

(defun +hunspell/add-word ()
  "Add word at point or selected region to excluded words file."
  (interactive)
  (let* ((word (if (use-region-p)
                   (buffer-substring-no-properties (region-beginning) (region-end))
                 (thing-at-point 'word t)))
         (file (concat no-littering-etc-directory "excluded-words.dic")))
    (if (not (and word (> (length word) 0)))
        (message "No word or region selected.")
      (with-temp-buffer
        (insert word "\n")
        (append-to-file (point-min) (point-max) file))
      (deactivate-mark) ;; 取消选中
      (message "Added '%s' to excluded words." word))))

(defvar +dict--db nil)

(add-hook 'emacs-startup-hook (lambda ()
                                (let ((db-path (expand-file-name "dict/stardict.db" no-littering-etc-directory)))
                                  (unless +dict--db
                                    (setq +dict--db (sqlite-open db-path))))))

(add-hook 'kill-emacs-hook (lambda ()
                             (when +dict--db
                               (sqlite-close +dict--db))))

(defun +dict--play-word-audio (word &optional player)
  "Play the pronunciation of WORD using PLAYER (e.g., 'mpv', 'mplayer')."
  (if (featurep 'cocoa)
      (call-process "say" nil 0 nil word)
    (let ((player-bin (executable-find (or player "mpv"))))
      (if player-bin
          (make-process
           :name "+dict/audio-play"
           :command (list player-bin
                          (format "http://dict.youdao.com/dictvoice?type=1&audio=%s"
                                  (url-hexify-string word)))
           :noquery t)
        (message "Audio player '%s' not found in PATH." player)))))

(defun +dict--show-posframe (text &optional position)
  "Show TEXT in a posframe near point."
  (let ((position (or position (point))))
    (posframe-show " *+dict/posframe*"
                   :string text
                   :position position
                   :max-width 30
                   :timeout 10
                   :border-width 1
                   :border-color "slate gray")
    (unwind-protect
        (push (read-event " ") unread-command-events)
      (posframe-hide " *+dict/posframe*"))))

;; 在线词典
(use-package fanyi
  :ensure t
  :commands(fanyi-dwim)
  :custom
  (fanyi-providers '(;; 海词
                     fanyi-haici-provider
                     ;; 有道同义词词典
                     fanyi-youdao-thesaurus-provider
                     ;; Etymonline
                     fanyi-etymon-provider
                     ;; Longman
                     fanyi-longman-provider))
  :config
  (setq fanyi-auto-select nil) ; 翻译后不跳转 *fanyi* buffer
  (setq fanyi-sound-player-support-https t))

(use-package shell-pop
  :ensure t
  :commands (shell-pop)
  :custom
  (shell-pop-shell-type '("eshell" "*eshell*" (lambda () (eshell)))))

(provide 'init-tools)
;;; init-tools.el ends here
